﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Hydra.Shell.Interfaces;
using System.Linq;
using Hydra.Shell.Attributes;
using SLControlLibrary;
using Hydra.Shell.Constants;
using System.Collections.Generic;
using Hydra.Shell.Exceptions;

namespace Hydra.Shell.Implementations
{
    public abstract class _HydraServiceBase
    {
        public static EventHandler Disposed { get; set; }
    }

    public abstract class HydraServiceBase<TService> : _HydraServiceBase, IQueryServiceProvider, Hydra.Shell.Interfaces.IServiceProvider where TService : IUnknown
    {
        protected UIThread uiThread;
        private bool isDisposed;
        private HydraShell hydraShell;
        private TService handledService;
        private bool isHandler;
        private IEnumerable<InterfaceTypePair> availableInterfaces;

        public HydraServiceBase(UIThread uiThread, TService handledService, IEnumerable<InterfaceTypePair> availableInterfaces)
        {
            this.uiThread = uiThread;
            this.handledService = handledService;
            this.availableInterfaces = availableInterfaces;

            isHandler = true;

            if (!(handledService is TService))
            {
                throw new InvalidCastException(string.Format("Service is not of the indicated type: {0}", typeof(TService).Name));
            }
        }

        public HydraServiceBase(UIThread uiThread, TService handledService)
        {
            this.uiThread = uiThread;
            this.handledService = handledService;

            isHandler = true;

            if (!(handledService is TService))
            {
                throw new InvalidCastException(string.Format("Service is not of the indicated type: {0}", typeof(TService).Name));
            }
        }

        public HydraServiceBase(UIThread uiThread)
        {
            this.uiThread = uiThread;

            if (!(this is TService))
            {
                throw new InvalidCastException(string.Format("Service is not of the indicated type: {0}", typeof(TService).Name));
            }
        }

        public HydraServiceBase(UIThread uiThread, HydraShell hydraShell)
        {
            this.uiThread = uiThread;
            this.hydraShell = hydraShell;

            if (!(this is TService))
            {
                throw new InvalidCastException(string.Format("Service is not of the indicated type: {0}", typeof(TService).Name));
            }
        }

        ~HydraServiceBase()
        {
            isDisposed = true;
            Disposed(this, EventArgs.Empty);
        }

        public virtual bool ServiceAvailableButNotLoaded(Guid guidService)
        {
            if (hydraShell != null)
            {
                return hydraShell.ServiceAvailableButNotLoaded(guidService);
            }
            else
            {
                return false;
            }
        }

        protected virtual IEnumerable<InterfaceTypePair> AvailableInterfaces
        {
            get
            {
                return availableInterfaces;
            }
        }

        public virtual bool QueryService<T>(Guid guidService, out ServiceLoadOperation<T> serviceLoadOperation) where T : IUnknown
        {
            serviceLoadOperation = new ServiceLoadOperation<T>(uiThread);
            var isService = typeof(TService).GetInterfaces().Any(i =>
            {
                return i.GUID == guidService || i.GetCustomAttributes(true).OfType<GuidAttribute>().Any(a =>
                {
                    return a == guidService;
                });
            });

            if (isHandler && isService && handledService is T)
            {
                serviceLoadOperation.DelaySetLoaded(handledService);

                return true;
            }
            else if (isService && this is T)
            {
                serviceLoadOperation.DelaySetLoaded(this);

                return true;
            }
            else if (hydraShell != null)
            {
                return hydraShell.QueryService<T>(guidService, out serviceLoadOperation);
            }
            else
            {
                serviceLoadOperation.DelaySetException(new Exception("Service not available"));
                return false;
            }
        }

        public virtual bool QueryService(Guid guidService, Guid guidInterFace, out ServiceLoadOperation serviceLoadOperation)
        {
            serviceLoadOperation = new ServiceLoadOperation(uiThread);
            var isService = typeof(TService).GetInterfaces().Any(i =>
            {
                return i.GUID == guidService || i.GetCustomAttributes(true).OfType<GuidAttribute>().Any(a =>
                {
                    return a == guidService;
                });
            });

            if (isService)
            {
                var pUnk = (IUnknown) null;

                if (isHandler)
                {
                    if (this.QueryInterface(guidInterFace, out pUnk))
                    {
                        serviceLoadOperation.DelaySetLoaded(handledService);
                    }
                    else
                    {
                        serviceLoadOperation.DelaySetException((Exception)pUnk);
                    }
                }
                else
                {
                    if (this.QueryInterface(guidInterFace, out pUnk))
                    {
                        serviceLoadOperation.DelaySetLoaded(handledService);
                    }
                    else
                    {
                        serviceLoadOperation.DelaySetException((Exception)pUnk);
                    }
                }

                return true;
            }
            else if (hydraShell != null)
            {
                return hydraShell.QueryService(guidService, guidInterFace, out serviceLoadOperation);
            }
            else
            {
                serviceLoadOperation.DelaySetException(new Exception("Service not available"));
                return false;
            }
        }

        public bool IsDisposed
        {
            get 
            {
                return isDisposed;
            }
        }

        public virtual bool QueryInterface(Guid iid, out IUnknown interfaceObject)
        {
            var isInterface = typeof(TService).GetInterfaces().Any(i =>
            {
                return i.GUID == iid || i.GetCustomAttributes(true).OfType<GuidAttribute>().Any(a =>
                {
                    return a == iid;
                });
            });

            if (isHandler)
            {
                if (isInterface)
                {
                    interfaceObject = handledService;
                    return true;
                }
                else if (this.AvailableInterfaces != null && this.AvailableInterfaces.Any(p => p.Interface.GUID == iid))
                {
                    var type = this.AvailableInterfaces.Single(p => p.Interface.GUID == iid).Implementation;

                    if (type == this.GetType())
                    {
                        interfaceObject = handledService;
                        return true;
                    }
                    else
                    {
                        interfaceObject = (IUnknown)Activator.CreateInstance(type);
                        return true;
                    }
                }
                else if (hydraShell != null)
                {
                    return hydraShell.QueryInterface(iid, out interfaceObject);
                }
                else
                {
                    interfaceObject = new CommonException(HydraConstants.E_NOINTERFACE);
                    return false;
                }
            }
            else
            {
                if (isInterface)
                {
                    interfaceObject = this;
                    return true;
                }
                else if (this.AvailableInterfaces != null && this.AvailableInterfaces.Any(p => p.Interface.GUID == iid))
                {
                    var type = this.AvailableInterfaces.Single(p => p.Interface.GUID == iid).Implementation;

                    if (type == this.GetType())
                    {
                        interfaceObject = this;
                        return true;
                    }
                    else
                    {
                        interfaceObject = (IUnknown)Activator.CreateInstance(type);
                        return true;
                    }
                }
                else if (hydraShell != null)
                {
                    return hydraShell.QueryInterface(iid, out interfaceObject);
                }
                else
                {
                    interfaceObject = new CommonException(HydraConstants.E_NOINTERFACE);
                    return false;
                }
            }
        }

        public virtual bool QueryInterface<T>(out T interfaceObject)
        {
            var iid = typeof(T).GetGuid();
            var isInterface = typeof(TService).GetInterfaces().Any(i =>
            {
                return i.GUID == iid || i.GetCustomAttributes(true).OfType<GuidAttribute>().Any(a =>
                {
                    return a == iid;
                });
            });

            if (isHandler)
            {
                if (isInterface)
                {
                    interfaceObject = (T)(object)handledService;
                    return true;
                }
                else if (this.AvailableInterfaces != null && this.AvailableInterfaces.Any(p => p.Interface.GUID == iid))
                {
                    var type = this.AvailableInterfaces.Single(p => p.Interface.GUID == iid).Implementation;

                    if (type == this.GetType())
                    {
                        interfaceObject = (T)(object)handledService;
                        return true;
                    }
                    else
                    {
                        interfaceObject = (T)Activator.CreateInstance(type);
                        return true;
                    }
                }
                else if (handledService is T)
                {
                    interfaceObject = (T)(object)handledService;
                    return true;
                }
                else if (hydraShell != null)
                {
                    return hydraShell.QueryInterface<T>(out interfaceObject);
                }
                else
                {
                    interfaceObject = (T)(object)new CommonException(HydraConstants.E_NOINTERFACE);
                    return false;
                }
            }
            else
            {
                if (isInterface)
                {
                    interfaceObject = (T)(object)this;
                    return true;
                }
                else if (this.AvailableInterfaces != null && this.AvailableInterfaces.Any(p => p.Interface.GUID == iid))
                {
                    var type = this.AvailableInterfaces.Single(p => p.Interface.GUID == iid).Implementation;

                    if (type == this.GetType())
                    {
                        interfaceObject = (T)(object)this;
                        return true;
                    }
                    else
                    {
                        interfaceObject = (T)Activator.CreateInstance(type);
                        return true;
                    }
                }
                else if (this is T)
                {
                    interfaceObject = (T)(object)this;
                    return true;
                }
                else if (hydraShell != null)
                {
                    return hydraShell.QueryInterface<T>(out interfaceObject);
                }
                else
                {
                    interfaceObject = (T)(object)new CommonException(HydraConstants.E_NOINTERFACE);
                    return false;
                }
            }
        }

        public virtual T GetService<T>()
        {
            var guidService = typeof(T).GetGuid();
            var isService = typeof(TService).GetInterfaces().Any(i =>
            {
                return i.GUID == guidService || i.GetCustomAttributes(true).OfType<GuidAttribute>().Any(a =>
                {
                    return a == guidService;
                });
            });

            if (isHandler)
            {
                if (isService && handledService is T)
                {
                    return (T)(object)handledService;
                }
                else if (this.ServiceAvailableButNotLoaded(guidService))
                {
                    var queryServiceProvider = (IQueryServiceProvider)null;

                    if (this.QueryInterface<IQueryServiceProvider>(out queryServiceProvider))
                    {
                        throw new ServiceAvailableButNotLoadedException(queryServiceProvider);
                    }
                    else
                    {
                        throw new ServiceAvailableButNotLoadedException();
                    }
                }
                else
                {
                    throw new Exception("Service not available");
                }
            }
            else
            {
                if (isService && this is T)
                {
                    return (T)(object)this;
                }
                else if (this.ServiceAvailableButNotLoaded(guidService))
                {
                    var queryServiceProvider = (IQueryServiceProvider)null;

                    if (this.QueryInterface<IQueryServiceProvider>(out queryServiceProvider))
                    {
                        throw new ServiceAvailableButNotLoadedException(queryServiceProvider);
                    }
                    else
                    {
                        throw new ServiceAvailableButNotLoadedException();
                    }
                }
                else
                {
                    throw new Exception("Service not available");
                }
            }
        }
    }
}
